package com.jweb.framework.ws;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.axiom.attachments.Attachments;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMException;
import org.apache.axiom.soap.SOAP11Version;
import org.apache.axiom.soap.SOAP12Version;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.SOAPMessage;
import org.apache.axiom.soap.impl.builder.MTOMStAXSOAPModelBuilder;
import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.ws.soap.SoapMessageFactory;
import org.springframework.ws.soap.SoapVersion;
import org.springframework.ws.soap.axiom.AxiomSoapMessageCreationException;
import org.springframework.ws.soap.support.SoapUtils;
import org.springframework.ws.transport.TransportInputStream;

import com.jweb.framework.core.util.XMLUtils;
import com.jweb.framework.ws.soap.axiom.AxiomSoapMessage;

/**
 * 修改人： Thomas
 * 修改内容：新增 
 * 类说明：解析SOAP报文工厂类
 */
public class AxiomSoapMessageFactoryJWeb implements InitializingBean , SoapMessageFactory {
	
private static final Log log = LogFactory.getLog(AxiomSoapMessageFactoryJWeb.class);
	
	private boolean payloadCaching = true ;
	
	private boolean attachmentCaching = false ;
	
	private File attachmentCacheDir ;
	
	private int attachmentCacheThreshold = 4096 ;
	
	private String encoding ;
	
	private SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory() ;
	
	/*static{
		try {
			if(Utils.OSGI){
				OMAbstractFactory.setMetaFactory(new OMLinkedListMetaFactory());
			}
		} catch (Throwable t) {
			log.warn("Old Axiom found in parent classloader, you could not use Axiom properly in OSGI: " + t);
		}
	}*/
	
	public void setCharsetEncoding(String encoding) {
		this.encoding = encoding ;
	}
	
	public void setPayloadCaching(boolean payloadCaching) {
		this.payloadCaching = payloadCaching ;
	}
	
	public void setAttachmentCaching(boolean attachmentCaching) {
		this.attachmentCaching = attachmentCaching ;
	}
	
	public void setAttachmentCacheDir(File attachmentCacheDir) {
		Assert.notNull(attachmentCacheDir , "'attachmentCacheDir' must not be null");
		
		Assert.isTrue(attachmentCacheDir.isDirectory() , "'attachmentCacheDir' must be a directory");
		// canWrite()：测试应用程序是否可以修改此抽象路径名表示的文件。
		Assert.isTrue(attachmentCacheDir.canWrite(), "'attachmentCacheDir' must be writable");
		this.attachmentCacheDir = attachmentCacheDir ;
	}
	
	public void setAttachmentCacheThreshold(int attachmentCacheThreshold) {
		Assert.isTrue(attachmentCacheThreshold > 0 , "'attachmentCacheThreshold' must be larger than 0");
		this.attachmentCacheThreshold = attachmentCacheThreshold ;
	}
	
	public void setSoapVersion(SoapVersion version) {
		if(SoapVersion.SOAP_11 == version){
			soapFactory = OMAbstractFactory.getSOAP11Factory();
		}
		else if(SoapVersion.SOAP_12 == version){
			soapFactory = OMAbstractFactory.getSOAP12Factory();
		}
		else {
			throw new IllegalArgumentException("Invalid version [" + version +"]. " + "Expected the SOAP_11 or SOAP_12 constant");
		}
	}
	
	public void afterPropertiesSet() throws Exception {
		if(log.isInfoEnabled()){
			log.info(payloadCaching ? "Enabled payload caching" : "Disabled payload caching");
		}
		if(attachmentCacheDir == null){
			String tempDir = System.getProperty("java.io.tmpdir");
			setAttachmentCacheDir(new File(tempDir));
		}
	}
	
	public AxiomSoapMessage createWebServiceMessage() {
		AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(this.soapFactory , this.payloadCaching , false);
		if(this.encoding != null){
			axiomSoapMessage.getAxiomMessage().setCharsetEncoding(this.encoding);
		}
		
		return axiomSoapMessage ;
	}
	
	public AxiomSoapMessage createWebServiceMessage(InputStream paramInputStream) throws IOException {
		// paramInputStream=org.springframework.ws.transport.AbstractSenderConnection$ResponseTransportInputStream
		Assert.isInstanceOf(TransportInputStream.class, paramInputStream , "AxiomSoapMessageFactory requires a TransportInputStream");
		TransportInputStream transportInputStream = (TransportInputStream) paramInputStream ;
		String contentType = getHeaderValue(transportInputStream , "Content-Type"); // contentType=null
		if(!StringUtils.hasLength(contentType)){ //true
			contentType = (this.soapFactory.getSOAPVersion() == SOAP11Version.getSingleton()) ? "text/xml" : "application/soap+xml";// contentType="text/xml"
		}
		
		String soapAction = getHeaderValue(transportInputStream , "SOAPAction");
		if(!StringUtils.hasLength(soapAction)){ // true
			soapAction = SoapUtils.extractActionFromContentType(contentType); // soapAction="""" 
		}
		try {
			if(isMultiPartRelated(contentType)){// false
				return createMultiPartAxiomSoapMessage(paramInputStream , contentType , soapAction);
			}
			return createAxiomSoapMessage(paramInputStream , contentType , soapAction);
		} catch(XMLStreamException ex) {
			throw new AxiomSoapMessageCreationException("Could not parse request: " + ex.getMessage() , ex);
		} catch(OMException ex) {
			throw new AxiomSoapMessageCreationException("Could not create message: " + ex.getMessage() , ex);
		}
	}
	
	private AxiomSoapMessage createAxiomSoapMessage(InputStream inputStream, String contentType, String soapAction) throws XMLStreamException {
		
		XMLStreamReader xmlStreamReader = XMLUtils.createXMLReader(inputStream , getCharSetEncoding(contentType), false);
		String envelopeNamespace = getSoapEnvelopeNamespace(contentType); 
		StAXSOAPModelBuilder builder = new StAXSOAPModelBuilder(xmlStreamReader , this.soapFactory , envelopeNamespace);
		SOAPMessage soapMessage = builder.getSoapMessage();
		
		return new AxiomSoapMessage(soapMessage , soapAction , this.payloadCaching , false) ;
	}

	/*private AxiomSoapMessage createMultiPartAxiomSoapMessage(InputStream inputStream, String contentType, String soapAction) throws XMLStreamException {
		
		XMLStreamReader xmlStreamReader = XMLUtils.createXMLReader(inputStream, getCharSetEncoding(contentType), false);
		String envelopeNamespace = getSoapEnvelopeNamespace(contentType);
		StAXSOAPModelBuilder stAXSOAPModelBuilder = new StAXSOAPModelBuilder(xmlStreamReader , this.soapFactory , envelopeNamespace);
		SOAPMessage soapMessage = stAXSOAPModelBuilder.getSoapMessage();
		
		return new AxiomSoapMessage(soapMessage , soapAction , this.payloadCaching , false) ;
	}*/
	
	private AxiomSoapMessage createMultiPartAxiomSoapMessage(InputStream inputStream, String contentType, String soapAction) throws XMLStreamException {
		Attachments attachments = new Attachments(inputStream, contentType,
				attachmentCaching, attachmentCacheDir.getAbsolutePath(),
				Integer.toString(attachmentCacheThreshold));
		XMLStreamReader xmlStreamReader = XMLUtils.createXMLReader(attachments.getSOAPPartInputStream(), getCharSetEncoding(attachments.getSOAPPartContentType()), false);
		String envelopeNamespace = getSoapEnvelopeNamespace(contentType);
		
		StAXSOAPModelBuilder builder;
		if(("text/xml".equals(attachments.getAttachmentSpecType())) || ("application/soap+xml").equals(attachments.getAttachmentSpecType())){
			builder = new StAXSOAPModelBuilder(xmlStreamReader, this.soapFactory, envelopeNamespace);
		}
		else if("application/xop+xml".equals(attachments.getAttachmentSpecType())){
			builder = new MTOMStAXSOAPModelBuilder(xmlStreamReader, attachments, envelopeNamespace);
		}
		else {
			throw new AxiomSoapMessageCreationException(
					"Unknown attachment type: ["
							+ attachments.getAttachmentSpecType() + "]");
		}
		
		return new AxiomSoapMessage(builder.getSoapMessage() , attachments , soapAction, this.payloadCaching , false) ;
	}

	private String getSoapEnvelopeNamespace(String contentType) {
		
		if(contentType.indexOf("text/xml")!=-1){
			return "http://schemas.xmlsoap.org/soap/envelope/";
		}
		if(contentType.indexOf("application/soap+xml")!=-1){
			return "http://www.w3.org/2003/05/soap-envelope";
		}
		
		return "http://schemas.xmlsoap.org/soap/envelope/";
	}

	private boolean isMultiPartRelated(String contentType) {
		
		return contentType.indexOf("multipart/related")!=-1 ;
	}

	private String getHeaderValue(TransportInputStream transportInputStream , String header) throws IOException {
		
		String contentType = null ;
		Iterator<String> iterator = transportInputStream.getHeaders(header);
		if(iterator.hasNext()){
			contentType = iterator.next();
		}
		return contentType ;
	}
	
	protected String getCharSetEncoding(String contentType) {
		
		if(this.encoding != null){
			return this.encoding ;
		}
		int i = contentType.indexOf("charset");
		if(i == -1){
			return "UTF-8";
		}
		int j = contentType.indexOf("=" , i);
		
		int k = contentType.indexOf(";" , j);
		String str ;
		if(k > 0){
			str = contentType.substring(j+1, k).trim();
		}
		else {
			str = contentType.substring(j+1, contentType.length()).trim();
		}
		
		if((str.charAt(0) == '"') && (str.charAt(str.length() - 1) == '"')){
			str = str.substring(1 , str.length()-1).trim();
		}
		if(("null".equalsIgnoreCase(str)) || (str.length()==0)){
			return "UTF-8" ;
		}
		
		return str ;
	}
	
	public String toString() {
        StringBuilder builder = new StringBuilder("AxiomSoapMessageFactory[");
        if (soapFactory.getSOAPVersion() == SOAP11Version.getSingleton()) {
            builder.append("SOAP 1.1");
        }
        else if (soapFactory.getSOAPVersion() == SOAP12Version.getSingleton()) {
            builder.append("SOAP 1.2");
        }
        builder.append(']');
        return builder.toString();
    }

}
